﻿#if NETSTANDARD2_0 || NETCOREAPP3_0
using Microsoft.Extensions.Logging;
#endif
using System;
using System.Collections.Specialized;
using System.Text;

namespace Longbow.Logging
{
    /// <summary>
    /// ILogger 文件日志内部实现类
    /// </summary>
    internal sealed class FileLogger : Logger
    {
        private StringBuilder _logBuilder;
        private readonly bool _includeScope;

#if NETSTANDARD2_0 || NETCOREAPP3_0
        /// <summary>
        /// 默认构造函数
        /// </summary>
        /// <param name="name">日志名称</param>
        /// <param name="logCallback">日志内容回调函数</param>
        /// <param name="filter">日志过滤器回调函数</param>
        /// <param name="scopeProvider">IExternalScopeProvider 实例</param>
        public FileLogger(string name, Action<string> logCallback, Func<string, LogLevel, bool> filter, IExternalScopeProvider scopeProvider) : base(name, logCallback, filter)
        {
            ScopeProvider = scopeProvider;
            _includeScope = ScopeProvider != null;
        }

        /// <summary>
        /// 写日志方法
        /// </summary>
        /// <typeparam name="TState">泛型类型</typeparam>
        /// <param name="logLevel">LogLevel 实例</param>
        /// <param name="eventId">eventId 实例</param>
        /// <param name="state">TState 实例</param>
        /// <param name="exception">Exception 实例</param>
        /// <param name="formatter">格式化方法回调函数</param>
        public override void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel)) return;

            var message = formatter?.Invoke(state, exception);
            if (!string.IsNullOrEmpty(message) || exception != null)
            {
                WriteMessage(logLevel, Name, eventId.Id, message, exception, state);
            }
        }

        private void GetScopeInformation(StringBuilder stringBuilder, string messagePadding)
        {
            if (ScopeProvider != null)
            {
                var initialLength = stringBuilder.Length;

                ScopeProvider.ForEachScope((scope, state) =>
                {
                    var (builder, length) = state;
                    var first = length == builder.Length;
                    builder.Append(first ? "=> " : " => ").Append(scope);
                }, (stringBuilder, initialLength));

                if (stringBuilder.Length > initialLength)
                {
                    stringBuilder.Insert(initialLength, messagePadding);
                    stringBuilder.AppendLine();
                }
            }
        }
#else
        /// <summary>
        /// 默认构造函数
        /// </summary>
        /// <param name="name">日志名称</param>
        /// <param name="logCallback">日志内容回调函数</param>
        /// <param name="filter">日志过滤器回调函数</param>
        /// <param name="includeScope">是否写入消息头 默认为 true 写入消息头</param>
        public FileLogger(string name, Action<string> logCallback, Func<string, LogLevel, bool> filter, bool includeScope = true) : base(name, logCallback, filter)
        {
            _includeScope = includeScope;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="logLevel"></param>
        /// <param name="message"></param>
        public override void Log(LogLevel logLevel, string message)
        {
            if (!IsEnabled(logLevel)) return;
            LogCallback?.Invoke(message);
        }
#endif

        private void WriteMessage<TState>(LogLevel logLevel, string logName, int eventId, string message, Exception exception, TState state)
        {
            if (_logBuilder == null) _logBuilder = new StringBuilder();

            var messagePadding = string.Empty;
            // 根据配置是否输出 Scope 信息
            if (_includeScope)
            {
                var logLevelString = GetLogLevelString(logLevel);

                // Example:
                // INFO: ConsoleApp.Program[10]
                //       Request received

                // category and event id
                _logBuilder.AppendFormat(string.Format("{0}: {1}[{2}]", logLevelString, logName, eventId));
                _logBuilder.AppendLine();

                // scope information
                messagePadding = new string(' ', logLevelString.Length + 2);

#if NETSTANDARD2_0 || NETCOREAPP3_0
                GetScopeInformation(_logBuilder, messagePadding);
#endif
            }

            if (!string.IsNullOrEmpty(message))
            {
                // add message padding
                if (_includeScope)
                {
                    message = $"{messagePadding}{message}".Replace(Environment.NewLine, $"{Environment.NewLine}{messagePadding}");
                }

                // write message
                _logBuilder.AppendLine(message);
            }


            // Example:
            // System.InvalidOperationException
            //    at Namespace.Class.Function() in File:line X
            if (exception != null)
            {
                // exception message
                _logBuilder.AppendLine(exception.FormatException(state as NameValueCollection));
            }

            if (_logBuilder.Length > 0)
            {
                LogCallback?.Invoke(_logBuilder.ToString());
            }

            _logBuilder.Clear();
            if (_logBuilder.Capacity > 1024) _logBuilder.Capacity = 1024;
        }

        private static string GetLogLevelString(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Trace:
                    return "trce";
                case LogLevel.Debug:
                    return "dbug";
                case LogLevel.Information:
                    return "info";
                case LogLevel.Warning:
                    return "warn";
                case LogLevel.Error:
                    return "fail";
                case LogLevel.Critical:
                default:
                    return "crit";
            }
        }
    }
}
